<?php
/**
 * A very light framework built on webappkit kits structure
 * Use/extend this class for application/request-handler kits
 * This file is part of Webappkit, a packages system for PHP web apps
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage engine
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */

/**
 * @property WakEngineResponse $response HTTP response container
 * @property string $_action action to render
 */
class WakEngineKit extends WakKit {

    /**
     * Serves http requests.
     * Splits request URI into segments
     * , and searches how to serve next path segment :
     * - explicit aliases
     * - self methods
     * - self attributes casted as WakEngineTool
     * - self subkits
     * @access public
     * @param UrlBean $uri
     * @return bool
     */
    function run($response=null) {
        // building response

        if (is_null($response))
            $response=new WakEngineResponse();
        $this->response=&$response;
        // pre-controller stuff
        if (!$this->_setUp()) {
            // rendering and sending error response
            $this->_sendResponse();

        } else {
            // action, controller stuff
            $this->_setAction();
            $this->_runActionController();
            // rendering and sending error response
            $this->_sendResponse();
        }
        // post-controller stuff
        $this->_tearDown();
    }

    /**
     * redirects to given url
     * @param string $url url to redirect to
     * @access protected
     */
    function _redirect($url) {
        header('location: '.$url);
        exit;
    }/**/

    /**
     * doing pre-controller stuff like authentication etc
     * @access protected
     */
    function _setUp() {
        return true;
    }/**/

    /**
     * doing post-controller, post-response
     * @access protected
     * @return bool
     */
    function _tearDown() {
        return true;
        // exit;
    }

    /**
     * throws error page
     * sets view to 'error' and sends response
     * @access protected
     * @param string $message
     * @param int $code
     */
    function _errorPage($message=null, $code=500) {
        $this->response->set('error',new WakEngineErrorPage($message, $code));
        $this->response->set('view','error');
        $this->response->set('status',$code);
        // echo "<p>error $code : $message</p><pre>"; print_r($this->response);
        // rendering and sending error response
        $this->_sendResponse();
        exit;
    }

    /**
     * sets action to perform.
     * @access protected
     * @param string $action if null, will shift a node from response
     * @param bool $force if true, will overwrite currently set action
     * @return bool
     */
    function _setAction($action=null,$force=false) {
        if ($this->_action and !$force)
            return false;
        if (!$action)
            // retrieving current node (uri segment) to serve
            $action=$this->response->shiftNode();
            // echo "<p>$action</p>";
        $this->_action=$action;
        $this->response->view=$action;
        return true;
    }

    /**
     * executes the appropriate action-controller for an uri node
     * @access protected
     * @return bool
     */
    function _runActionController() {
        $node=$this->_action; // echo "<p>$node</p>";
        // searching for alias
        // searching for self method
        if (method_exists($this,$node)) {
            return $this->_runMethodController($node);
        }
        // searching for self tool attribute
        /*if ($r=$this->runToolController($response)) {
            return $r;
        }
        // searching for self subkit
        if ($r=$this->runSubkitController($response)) {
            return $r;
        }*/
        // searching for custom class controller
        if ($this->_searchClassController($node)) {
            return $this->_runClassController($node);
        }
        // using fall-back controller if exists
        if (method_exists($this,'_fallbackController')
        and $result=$this->_fallbackController($node)) {
            return $result;
        }
        // throw new WakEngineErrorPage("No controller for $node",404);
        return false;
    }

    /**
     * runs an own method as controller to serve current node
     * @access protected
     * @param string $method
     * @param WakEngineResponse $response
     * @return bool
     */
    function _runMethodController($method) {
        // getting remaining nodes
        $nodes=$this->response->getNextNodes();
        switch (count($nodes)) {
            case 0:
                return $this->$method();
            case 1:
                return $this->$method($nodes[0]);
            case 2:
                return $this->$method($nodes[0],$nodes[1]);
            case 3:
                return $this->$method($nodes[0],$nodes[1],$nodes[2]);
            case 4:
                return $this->$method($nodes[0],$nodes[1],$nodes[2],$nodes[3]);
            default:
                return false;
        }
    }

    /**
     * search for a custom class controller
     * @access private
     * @param string $name
     * @return string controller's class definition file's name
     */
    function _searchClassController($name) {
        $file=$this->getPath()
        .'controllers/'
        .$name.'.php4';
        if (!file_exists($file))
            return false;
        return $file;
    }

    /**
     * runs a custom class controller to serve current node
     * @access protected
     * @param string $name controller's name
     * @return bool
     */
    function _runClassController($name) {
        // including definition file
        if (!$file=$this->_searchClassController($name)) {
            //throw new WakEngineErrorPage('missing controller file',500);
            return false;
        }
        include($file);
        // checking class
        $class=$name.'_Controller';
        if (!class_exists($class)) {
            //throw new WakEngineErrorPage('controller class not defined',500);
            return false;
        }

        $controller=new $class($this);
        return $controller->run($this->response);
    }

    /**
     * search for an own attribute controller to serve current node.
     * Will use only WakEngineTool instances
     * @access protected
     * @param UrlBean $uri uri to serve
     * @return bool
     * /
    function runToolController($uri) {
        // getting next node to serve
        $tool=$this->node;
        if (!is_a($this->$tool,'WakEngineTool'))
            return false;
        // shortening uri
        $uri=$this->_shortenUri($uri);
        return $this->$tool->run($uri);
    }/**/

    /**
     * search for an subkit controller to serve current node
     * @access protected
     * @param UrlBean $uri uri to serve
     * @return bool
     * /
    function runSubkitController($uri) {
        // getting next node to serve
        $id=$this->node;
        if (!in_array($id,$this->listSubkits())
        or !$kit=&$this->getSubkit($id))
            return false;
        // shortening uri
        $uri=$this->_shortenUri($uri);
        return $kit->run($uri);
    }/**/

    /**
     * Search for view to render response, fills and sends it
     * @todo correct http status header
     * @access protected
     * @param WakEngienResponse
     * @return bool
     */
    function _sendResponse() {
        $view=$this->response->view;
        // searching for a template to render
        if ($this->_sendTemplate($view))
            return true;
        // searching for a view
        if ($this->_sendPHPView($view,$this->getPath().'views/'))
            return true;
        // defaulting to engine views
        $engine=Webappkit::getKit('webappkit.engine');
        if ($this->_sendPHPView($view,$engine->getPath().'views/'))
            return true;
        trigger_error("no view for $view");
        // throw new WakEngineErrorPage("no view for $view",500);
        return false;
    }

    /**
     * search, feed and send a template for the view
     * searches into current kit templates storage tool
     * @access private
     * @param string $view
     * @return bool
     */
    function _sendTemplate($view) {
        // is there a valid templates storage tool ?
        if (!$this->templates or !is_a($this->templates,'WakStorage'))
            return false;
        // i18n
        $this->templates->setCriteria('lang',$this->response->lang);
        // searching template
        if (!$tpl=$this->templates->get($view) or !is_a($tpl,'DjTplTemplate')) {
            trigger_error('no template for '.$view,E_USER_NOTICE);
            return false;
        }
        echo $tpl->render($this->response);
        return true;
    }

    /**
     * search , feed and send a brute PHP view
     * @access private
     * @param string $view
     * @param string $path must include trailing slash
     * @return bool
     */
    function _sendPHPView($view,$path) {
        $file=$path.$view.'.php'; // echo "<p>$file</p>";
        if (!file_exists($file))
            return false;
        include($file);
        return true;
    }

    /**
     * sets a value inside response
     * @access protected
     * @param string $key
     * @param mixed $value
     * @return bool
     */
    function _setResponseElement($key, $value) {
        return $this->response->set($key,$value);
    }

    /**
     * @var string action to perform
     */
    var $_action;

}

/**
 * Exceptions
 */
class WakEngineErrorPage {

    function WakEngineErrorPage($msg,$code=500) {
        $this->msg=$msg;
        $this->code=$code;
    }

    function getMessage() {
        return $this->msg;
    }

    function getCode() {
        return $this->code;
    }
}
